---
description: 'Improve error handling in GraphQL with Sofa. Learn how to add a custom errorHandler function to log and format errors for a better user experience. #GraphQL #Sofa'
---

# Error Handling

By default, Sofa returns a response that includes JSON representation of thrown error object from GraphQL with HTTP status code 500. But, you can enhance error handler by adding your `errorHandler` function.

```typescript
api.use(
  '/api',
  useSofa({
    schema,
    // `errors` is the array containing the `Error` objects
    errorHandler(errors) {
      for (const error of errors) {
        console.error(`Error: ${error.message}`);
      }
      return new Response(errs[0].message, {
        status: 500,
        headers: { 'Content-Type': 'application/json' },
      });
    },
  })
);
```

By default, it always returns a response with `200` if the request is valid.
If the request is invalid, it returns a response with `400` status code and the error message.

```ts
const res = await fetch('http://localhost:4000/api/createUser', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 1, // Invalid name
  }),
});

console.log(res.status); // 400
const data = await res.json();
console.log(data); // {"errors":[{"message":"Expected type String, found 1."}]}
```

## HTTP Error Extensions

Just like GraphQL Yoga's [error handling](https://the-guild.dev/graphql/yoga-server/docs/features/error-masking#modifying-http-status-codes-and-headers) , SOFA respects the status code and headers provided in the error extensions.

```ts filename="GraphQL Error with http extensions." {6-11}
throw new GraphQLError(
  `User with id '${args.byId}' not found.`,
  // error extensions
  {
    extensions: {
      http: {
        status: 400,
        headers: {
          'x-custom-header': 'some-value',
        },
      },
    },
  }
);
```

In this case, you returns a response with `400` status code and `x-custom-header` in the response headers.

Let's say you have a simple GraphQL API like below;

```ts
import { createServer } from 'node:http';
import { useSofa } from 'sofa-api';
import { makeExecutableSchema } from '@graphql-tools/schema';

createServer(
  useSofa({
    basePath: '/api',
    schema: makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          posts: [Post!]!
        }
        type Post {
          id: ID!
          title: String!
          secret: String!
        }
      `,
      resolvers: {
        Query: {
          posts() {
            return getPosts();
          },
        },
        Post: {
          async secret(_, __, { request }) {
            const authHeader = request.headers.get('Authorization');
            if (!authHeader) {
              throw new GraphQLError('Unauthorized', {
                extensions: {
                  http: {
                    status: 401,
                    headers: {
                      'WWW-Authenticate': 'Bearer',
                    },
                  },
                },
              });
            }
            const [type, token] = authHeader.split(' ');
            if (type !== 'Bearer') {
              throw new GraphQLError('Invalid token type', {
                extensions: {
                  http: {
                    status: 401,
                    headers: {
                      'WWW-Authenticate': 'Bearer',
                    },
                  },
                },
              });
            }
            if (token !== 'secret') {
              throw new GraphQLError('Invalid token', {
                extensions: {
                  http: {
                    status: 401,
                    headers: {
                      'WWW-Authenticate': 'Bearer',
                    },
                  },
                },
              });
            }
            return 'Secret value';
          },
        },
      },
    }),
  })
).listen(4000);
```

In this case if you make a request to `/api/posts` without a valid `Authorization` header, 
you will get a response with `401` status code and `WWW-Authenticate` in the response headers.
But the response body will contain the data and errors.

```ts
const res = await fetch('http://localhost:4000/api/posts');
console.log(res.status); // 401
console.log(res.headers.get('WWW-Authenticate')); // Bearer
const data = await res.json();
expect(data).toEqual({
  data: {
    posts: [
      {
        id: '1',
        title: 'Post 1',
        secret: null,
      },
      {
        id: '2',
        title: 'Post 2',
        secret: null,
      },
    ],
  },
  errors: [
    {
      message: 'Unauthorized',
      path: ['posts', 'secret'],
    },
  ],
});
```

In this case only errored fields will be `null` in the response body.

However if you make a request to `/api/me` with `x-user-id` header, you will get a response with `200` status code and `x-custom-header` in the response headers.

```ts
const res = await fetch('http://localhost:4000/api/posts', {
  headers: {
    Authorization: 'Bearer secret',
  },
});
console.log(res.status); // 200
const data = await res.json();
expect(data).toEqual({
  data: {
    posts: [
      {
        id: '1',
        title: 'Post 1',
        secret: 'Secret value',
      },
      {
        id: '2',
        title: 'Post 2',
        secret: 'Secret value',
      },
    ],
  },
});
```
